package tiltmaze.solver;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import tiltmaze.Maze;

/**
 * Analyzer. Uses maze solver to find out various statistics of a maze.
 * @author Juraj Nižnan
 */
public class MazeStatesAnalyzer implements MazeStatesInterface {

    Maze maze;
    private int nodeCounter = 0;
    private int edgeCounter = 0;
    private int nodeBackCounter = 0;
    private int edgeBackCounter = 0;
    private int simplifiedGraphEdgeCounter = 0;
    private int simplifiedGraphDeadEdgeCounter = 0;
    MazeSolver mazeSolver;
    HashSet<MazePath> visitedPathsBackwards;
    MazePath startNode;
    MazeStateGraph graph;

    public MazeStatesAnalyzer(Maze maze) {
        this.maze = maze;
    }

    public void analyze() {
        mazeSolver = new MazeSolver(maze);
        mazeSolver.stateGraph = this;
        mazeSolver.solve();
        MazeMotionGraphInverted mmgi = new MazeMotionGraphInverted(mazeSolver.motion);
        visitedPathsBackwards = new HashSet<MazePath>(mazeSolver.endedPaths);
        HashSet<MazePath> paths = new HashSet<MazePath>(mazeSolver.endedPaths);
        while (!paths.isEmpty()) {
            HashSet<MazePath> paths1 = new HashSet<MazePath>();
            for (MazePath path : paths) {
                VertexInverted vi = mmgi.vertexMap.get(path.lastVertex.position);
                for (VertexInverted vi1 : vi.sources.keySet()) {
                    for (Integer ch : vi.sources.get(vi1)) {
                        MazePath mp = new MazePath();
                        mp.lastVertex = new Vertex(vi1.position);
                        mp.checkpointsCollected = path.checkpointsCollected & (0xFFFFFFF ^ ch);
                        if (mazeSolver.visitedPaths.contains(mp)) {
                            edgeBackCounter++;
                            if (!visitedPathsBackwards.contains(mp)) {
                                paths1.add(mp);
                                visitedPathsBackwards.add(mp);
                            }
                        }
                    }
                }
            }
            paths = paths1;
        }
        nodeBackCounter = visitedPathsBackwards.size();
        //GENERATE SIMPLIFIED STATE GRAPH:
        graph = new MazeStateGraph(maze);
        graph.addStartNode(startNode);
        paths = new HashSet<MazePath>();
        HashSet<MazePath> visitedPaths = new HashSet<MazePath>();
        paths.add(startNode);
        visitedPaths.add(startNode);
        while (!paths.isEmpty()) {
            HashSet<MazePath> paths1 = new HashSet<MazePath>();
            for (MazePath path : paths) {
                for (int i = 0; i < 4; i++) {
                    if (path.lastVertex.targets[i] != null) {
                        MazePath path1 = new MazePath();
                        path1.lastVertex = path.lastVertex.targets[i];
                        path1.path = new ArrayList<Integer>(Collections.unmodifiableList(path.path));
                        path1.path.add(i);
                        path1.checkpointsCollected = path.checkpointsCollected | path.lastVertex.edges[i];
                        if (visitedPathsBackwards.contains(path1)) {
                            if (!visitedPaths.contains(path1)) {
                                boolean ended1 = mazeSolver.ended(path1);
                                if (!ended1) {
                                    paths1.add(path1);
                                }
                                graph.addNode(path1, ended1);
                            }
                            graph.addEdge(path, path1, i);
                            simplifiedGraphEdgeCounter++;
                        } else {
                            graph.addDeadlockEdge(path, i);
                            simplifiedGraphDeadEdgeCounter++;
                            simplifiedGraphEdgeCounter++;
                        }
                        visitedPaths.add(path1);
                    }
                }
            }
            paths = paths1;
        }
    }

    public void addEdge(MazePath p0, MazePath p1, int move) {
        edgeCounter++;
    }

    public void addStartNode(MazePath startPath) {
        startNode = startPath;
        nodeCounter++;
    }

    public void addNode(MazePath p, boolean ended) {
        nodeCounter++;

    }

    public String getAnalysis() {
        String ret = "Edges: " + edgeCounter + "\n";
        ret += "Nodes: " + nodeCounter + "\n";
        ret += "Edges backwards: " + edgeBackCounter + "\n";
        ret += "Nodes backwards: " + nodeBackCounter + "\n";
        ret += "Edge deadlocks: " + getEdgeDeadlocks() + "\n";
        ret += "Node deadlocks: " + getNodeDeadlocks() + "\n";
        ret += "Simplified graph dead edges (rel.): " + getSimplifiedGraphEdgeDeadlocks() + "\n";
        ret += "Simplified graph dead edges (abs.): " + getSimplifiedGraphEdgeDeadlocksCount() + "\n";
        ret += "\nSolutions:\n";
        ret += mazeSolver.getPaths();
        ret += "\nTime: " + mazeSolver.getTime() + " ms";
        return ret;
    }

    public int getEdgeBackCounter() {
        return edgeBackCounter;
    }

    public int getEdgeCounter() {
        return edgeCounter;
    }

    public int getNodeBackCounter() {
        return nodeBackCounter;
    }

    public int getNodeCounter() {
        return nodeCounter;
    }

    public double getEdgeDeadlocks() {
        if (edgeCounter == 0) {
            return 0;
        } else {
            return (double) (edgeCounter - edgeBackCounter) / (double) edgeCounter;
        }
    }

    public int getEdgeDeadlockCounter() {
        return edgeCounter - edgeBackCounter;
    }

    public double getNodeDeadlocks() {
        if (nodeCounter == 0) {
            return 0;
        } else {
            return (double) (nodeCounter - nodeBackCounter) / (double) nodeCounter;
        }
    }

    public int getNodeDeadlockCounter() {
        return nodeCounter - nodeBackCounter;
    }

    public double getSimplifiedGraphEdgeDeadlocks() {
        return (double) simplifiedGraphDeadEdgeCounter / (double) simplifiedGraphEdgeCounter;
    }

    public int getSimplifiedGraphEdgeDeadlocksCount() {
        return simplifiedGraphDeadEdgeCounter;
    }

    public int getSimplifiedGraphEdgeCount() {
        return simplifiedGraphEdgeCounter;
    }

    public int getShortestSolutionLength() {
        return mazeSolver.getShortestSolutionLength();
    }

    public void writeGML(String filename) throws IOException {
        graph.writeGML(filename);
    }

}
